package cn.matezk.lianlian.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.matezk.lianlian.beans.Constant;
import cn.matezk.lianlian.beans.NotifyConfig;
import cn.matezk.lianlian.dao.*;
import cn.matezk.lianlian.framework.RedisMutexLock;
import cn.matezk.lianlian.model.*;
import cn.matezk.lianlian.service.IEventService;
import cn.matezk.lianlian.service.INotifyService;
import cn.matezk.lianlian.utils.StringUtils;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.sdk.service.dyvmsapi20170525.AsyncClient;
import com.aliyun.sdk.service.dyvmsapi20170525.models.SingleCallByTtsRequest;
import com.aliyun.sdk.service.dyvmsapi20170525.models.SingleCallByTtsResponse;
import com.tencentcloudapi.iotexplorer.v20190423.IotexplorerClient;
import com.tencentcloudapi.iotexplorer.v20190423.models.ControlDeviceDataRequest;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;

@Service
@Slf4j
public class EventService implements IEventService {
    @Autowired
    private RedisMutexLock mutexLock;
    @Autowired
    private DeviceDao deviceDao;
    @Autowired
    private UserDao userDao;
    @Autowired
    private NotifyRecordDao notifyRecordDao;
    @Autowired
    private EventDao eventDao;
    @Autowired
    private INotifyService notifyService;
    @Autowired
    private WxMpService wxMpService;
    @Autowired
    private Client client;
    @Autowired
    private AsyncClient asyncClient;
    @Autowired
    private IotexplorerClient tencentClient;
    @Autowired
    private SecretKeyDao secretKeyDao;
    @Autowired
    private ApiPushService apiPushService;
    @Value("${aliyun.tts.called-show-number}")
    private String showNumber;
    @Value("${miniapp.appid}")
    private String appId;
    @Value("${tencent.notify-device}")
    private String notifyDevice;

    @Override
    public void handle(String productId, String deviceName, String eventId, String type, Date eventTime, JSONObject params) throws InterruptedException {
        String deviceId = productId.concat("/").concat(deviceName);
        apiPush(deviceId, eventId, eventTime, params);

        // 是否绑定APPKEY
        SecretKey secretKey = secretKeyDao.deviceMatch(productId, deviceName);
        // 是否绑定了用户
        Long userId = ObjectUtil.isNull(secretKey) ? null : secretKey.getBindUser();

        String amountLockKey = Device.createLockKey(deviceId);
        if (ObjectUtil.isNotNull(userId)) {
            amountLockKey = String.format("lock.user.%d", userId);
        }
        String primaryKey = IdUtil.fastSimpleUUID();
        Event event = new Event();
        event.setId(primaryKey);
        event.setEventId(eventId);
        event.setType(type);
        event.setDeviceId(deviceId);
        event.setParams(params);
        event.setEventTime(eventTime);
        eventDao.insertSelective(event);

        if (ObjectUtil.equals("power_on", eventId)) {
            notifyRecordDao.cancel(deviceId, "power_off");
        } else if (ObjectUtil.equals("power_off", eventId)) {
            notifyRecordDao.cancel(deviceId, "power_on");
        }

        if (mutexLock.tryLock(amountLockKey, 10)) {
            try {
                Device device = deviceDao.selectByPrimaryKey(deviceId);
                // 断电报警器事件触发云喇叭播报
                if ("YXENYPLCQH".equals(deviceId.split("/")[0]) && ("power_on".equals(eventId) || "power_off".equals(eventId))) {
                    if (ObjectUtil.isNull(device) || isToday(device.getPrintTime())) {
                        // 云喇叭播报
                        ControlDeviceDataRequest request = new ControlDeviceDataRequest();
                        request.setProductId(notifyDevice.split("/")[0]);
                        request.setDeviceName(notifyDevice.split("/")[1]);
                        String text = String.format("[n1]%s%s", shortName(deviceId.split("/")[1]), "power_on".equals(eventId) ? "来电了" : "停电了");
                        request.setData(JSONUtil.createObj().set("text", text).set("times", 1).toString());
                        try {
                            tencentClient.ControlDeviceData(request);
                        } catch (Exception e) {
                            log.error("云喇叭播报失败: {}", e.getMessage());
                        }
                    }
                }

                // 设备未绑定或者配置为空 直接返回
                if (ObjectUtil.isNull(device) || ObjectUtil.isNull(device.getNotifyConfig())) return;

                // 安徽雪友科技设备10分钟内不重复通知
                if (ObjectUtil.isNotNull(secretKey) && ObjectUtil.equals(secretKey.getAppKey(), "AHXYKJ")) {
                    if (!mutexLock.lock(String.format("event.%s.%s", productId, deviceName), 60 * 10 * 1000)) {
                        mutexLock.unlock(amountLockKey);
                        return;
                    }
                }

                NotifyConfig config = device.getNotifyConfig();
                // 可用参数
                params.set("deviceId", deviceId)
                        .set("eventKey", eventId)
                        .set("aliasName", device.getAliasName())
                        .set("deviceName", device.getDeviceName())
                        .set("dataTime", DateUtil.format(eventTime, DatePattern.NORM_DATETIME_PATTERN));
                int amount = device.getAmount();
                if (ObjectUtil.isNotNull(userId)) {
                    // 用户余额
                    amount = userDao.userAmount(userId);
                }

                // 短信推送
                NotifyConfig.Event eventConfig = config.matchEvent(eventId);
                if (Boolean.TRUE.equals(eventConfig.getSmsEnable()) && ObjectUtil.isNotEmpty(config.getMobile())) {
                    try {
                        amount = smsNotify(ObjectUtil.isNull(secretKey) ? "" : secretKey.getAppKey(), primaryKey, deviceId, eventId, params, config.getMobile(), eventConfig.getRepeatInterval(), Boolean.TRUE.equals(eventConfig.getRepeatEnable()) ? eventConfig.getRepeatTimes() : 0, amount);
                    } catch (Exception e) {
                        log.error(e.getMessage());
                    }
                }

                // 电话推送
                if (Boolean.TRUE.equals(config.voiceEnable(eventId)) && ObjectUtil.isNotEmpty(config.getMobile())) {
                    try {
                        amount = voiceNotify(ObjectUtil.isNull(secretKey) ? "" : secretKey.getAppKey(), primaryKey, deviceId, eventId, params, config.getMobile(), eventConfig.getRepeatInterval(), Boolean.TRUE.equals(eventConfig.getRepeatEnable()) ? eventConfig.getRepeatTimes() : 0, amount);
                    } catch (Exception e) {
                        log.error(e.getMessage());
                    }
                }

                // 微信推送
                if (Boolean.TRUE.equals(config.wechatEnable(eventId))) {
                    params.set("amount", StringUtils.amount(amount));
                    try {
                        wechatNotify(ObjectUtil.isNull(secretKey) ? "" : secretKey.getAppKey(), primaryKey, deviceId, eventId, params, eventConfig.getRepeatInterval(), Boolean.TRUE.equals(eventConfig.getRepeatEnable()) ? eventConfig.getRepeatTimes() : 0);
                    } catch (Exception e) {
                      log.error(e.getMessage());
                    }
                }

                // 修改设备金额
                if (ObjectUtil.isNull(userId)) {
                    if (device.getAmount().intValue() != amount) {
                        deviceDao.updateAmount(deviceId, amount);
                    }
                } else {
                    userDao.updateAmount(userId, amount);
                }
            } finally {
                mutexLock.unlock(amountLockKey);
            }
        }
    }

    private void wechatNotify(String appKey, String eventId, String deviceId, String eventKey, JSONObject params, int interval, int times) {
        NotifyTemplate wechatTemplate = notifyService.matchTemplate(appKey, NotifyTemplate.METHOD_WECHAT, eventKey);
        if (ObjectUtil.isEmpty(wechatTemplate)) return;
        String variable = wechatTemplate.getParams();
        String url = wechatTemplate.getUrl();
        String content = wechatTemplate.getContent();
        for (String key : params.keySet()) {
            variable = variable.replace(StringUtils.createVariable(key), params.getStr(key));
            content = content.replace(StringUtils.createVariable(key), params.getStr(key));
            url = url.replace(StringUtils.createVariable(key), params.getStr(key));
        }
        JSONObject pushParams = JSONUtil.parseObj(variable);
        List<WxMpTemplateData> datas = new ArrayList<>();
        for (String key : pushParams.keySet()) {
            datas.add(new WxMpTemplateData(key, pushParams.getStr(key)));
        }
        List<String> openIds = notifyService.wechatNotifyUser(deviceId);
        if (ObjectUtil.isEmpty(openIds))    return;
        WxMpTemplateMessage.MiniProgram miniProgram = new WxMpTemplateMessage.MiniProgram();
        miniProgram.setAppid(appId);
        miniProgram.setUsePath(false);
        pushParams.set("url", url);
        for (String openid : openIds) {
            WxMpTemplateMessage message = WxMpTemplateMessage.builder()
                    .toUser(openid)
                    .templateId(wechatTemplate.getTemplateCode())
                    .data(datas)
                    .url(url)
//                    .miniProgram(miniProgram)
                    .build();
            NotifyRecord record = new NotifyRecord();
            record.setEventId(eventId);
            record.setDeviceId(deviceId);
            record.setMethod(NotifyTemplate.METHOD_WECHAT);
            record.setTemplateCode(wechatTemplate.getTemplateCode());
            record.setToUser(openid);
            record.setParams(pushParams.toString());
            record.setContent(content);
            try {
                // 微信推送
                String bizId = wxMpService.getTemplateMsgService().sendTemplateMsg(message);
                record.setBizId(bizId);
                record.setStatus(NotifyRecord.STATUS_SUCCESS);
            } catch (WxErrorException e) {
                record.setStatus(NotifyRecord.STATUS_FAILED);
                record.setErrorCode(String.valueOf(e.getError().getErrorCode()));
                record.setErrorMsg(StringUtils.sub(e.getError().getErrorMsg(), NotifyRecord.ERROR_MSG_LEN));
                log.error(e.getMessage());
            } catch (Exception e) {
                record.setStatus(NotifyRecord.STATUS_FAILED);
                record.setErrorMsg(StringUtils.sub(e.getMessage(), NotifyRecord.ERROR_MSG_LEN));
            } finally {
                Calendar calendar = Calendar.getInstance();
                record.setNotifyTime(calendar.getTime());
                notifyRecordDao.insertSelective(record);

                // 重复推送
                for (int i = 0; i < times; i ++) {
                    calendar.add(Calendar.MINUTE, interval);
                    NotifyRecord preNotify = new NotifyRecord();
                    preNotify.setEventId(eventId);
                    preNotify.setDeviceId(deviceId);
                    preNotify.setToUser(record.getToUser());
                    preNotify.setMethod(record.getMethod());
                    preNotify.setTemplateCode(record.getTemplateCode());
                    preNotify.setStatus(NotifyRecord.STATUS_WAIT);
                    preNotify.setParams(record.getParams());
                    preNotify.setContent(record.getContent());
                    preNotify.setNotifyTime(calendar.getTime());
                    notifyRecordDao.insertSelective(preNotify);
                }
            }
        }
    }

    private int smsNotify(String appKey, String eventId, String deviceId, String eventKey, JSONObject params, List<String> mobiles, int interval, int times, int amount) {
        NotifyTemplate smsTemplate = notifyService.matchTemplate(appKey, NotifyTemplate.METHOD_SMS, eventKey);
        if (ObjectUtil.isEmpty(smsTemplate)) return amount;
        List<String> notifyMobile = new ArrayList<>();
        for (String mobile : mobiles) {
            if (ObjectUtil.isEmpty(mobile)) continue;
            if (amount >= Constant.SMS_UNIT_PRICE) {
                amount -= Constant.SMS_UNIT_PRICE;
                notifyMobile.add(mobile);
            }
        }
        if (ObjectUtil.isEmpty(notifyMobile)) return amount;
        for (String mobile : notifyMobile) {
            NotifyRecord record = new NotifyRecord();
            record.setEventId(eventId);
            record.setDeviceId(deviceId);
            record.setToUser(mobile);
            record.setMethod(NotifyTemplate.METHOD_SMS);
            record.setTemplateCode(smsTemplate.getTemplateCode());
            try {
                SendSmsRequest request = new SendSmsRequest();
                request.setPhoneNumbers(mobile);
                request.setSignName(smsTemplate.getSign());
                request.setTemplateCode(smsTemplate.getTemplateCode());
                String variable = smsTemplate.getParams();
                String content = smsTemplate.getContent();
                for (String key : params.keySet()) {
                    variable = variable.replace(StringUtils.createVariable(key), params.getStr(key));
                    content = content.replace(StringUtils.createVariable(key), params.getStr(key));
                }
                record.setParams(variable);
                record.setContent(content);
                request.setTemplateParam(record.getParams());
                request.setOutId("lianlian");
                SendSmsResponse response = client.sendSms(request);
                record.setBizId(response.getBody().getBizId());
                if (ObjectUtil.notEqual("OK", response.getBody().getCode())) {
                    // 发送失败
                    record.setStatus(NotifyRecord.STATUS_FAILED);
                    record.setErrorCode(StringUtils.sub(response.getBody().getCode(), NotifyRecord.ERROR_CODE_LEN));
                    record.setErrorMsg(StringUtils.sub(response.getBody().getMessage(), NotifyRecord.ERROR_MSG_LEN));
                } else {
                    // 待回执
                    record.setStatus(NotifyRecord.STATUS_CONFIRM);
                }
            } catch (Exception e) {
                log.error(e.getMessage());
                record.setStatus(NotifyRecord.STATUS_FAILED);
                record.setErrorMsg(StringUtils.sub(e.getMessage(), NotifyRecord.ERROR_MSG_LEN));
                amount += Constant.SMS_UNIT_PRICE;
            } finally {
                Calendar calendar = Calendar.getInstance();
                record.setNotifyTime(calendar.getTime());
                notifyRecordDao.insertSelective(record);
                // 重复推送
                for (int i = 0; i < times; i ++) {
                    calendar.add(Calendar.MINUTE, interval);
                    NotifyRecord preNotify = new NotifyRecord();
                    preNotify.setEventId(record.getEventId());
                    preNotify.setDeviceId(record.getDeviceId());
                    preNotify.setToUser(record.getToUser());
                    preNotify.setMethod(record.getMethod());
                    preNotify.setTemplateCode(record.getTemplateCode());
                    preNotify.setStatus(NotifyRecord.STATUS_WAIT);
                    JSONObject templateParams = JSONUtil.parseObj(record.getParams());
                    templateParams.set("sign", smsTemplate.getSign());
                    preNotify.setParams(templateParams.toString());
                    preNotify.setContent(record.getContent());
                    preNotify.setNotifyTime(calendar.getTime());
                    notifyRecordDao.insertSelective(preNotify);
                }
            }
        }
        return amount;
    }

    private int voiceNotify(String appKey, String eventId, String deviceId, String eventKey, JSONObject params, List<String> mobiles, int interval, int times, int amount) {
        NotifyTemplate voiceTemplate = notifyService.matchTemplate(appKey, NotifyTemplate.METHOD_VOICE, eventKey);
        if (ObjectUtil.isEmpty(voiceTemplate)) return amount;
        List<String> notifyMobile = new ArrayList<>();
        for (String mobile : mobiles) {
            if (ObjectUtil.isEmpty(mobile)) continue;
            if (amount < Constant.TTS_UNIT_PRICE) break;
            amount -= Constant.TTS_UNIT_PRICE;
            notifyMobile.add(mobile);
        }
        for (String mobile : notifyMobile) {
            NotifyRecord record = new NotifyRecord();
            record.setEventId(eventId);
            record.setDeviceId(deviceId);
            record.setToUser(mobile);
            record.setMethod(NotifyTemplate.METHOD_VOICE);
            record.setTemplateCode(voiceTemplate.getTemplateCode());
            String variable = voiceTemplate.getParams();
            String content = voiceTemplate.getContent();
            for (String key : params.keySet()) {
                variable = variable.replace(StringUtils.createVariable(key), params.getStr(key));
                content = content.replace(StringUtils.createVariable(key), params.getStr(key));
            }
            record.setParams(variable);
            record.setContent(content);
            try {
                SingleCallByTtsRequest singleCallByTtsRequest = SingleCallByTtsRequest.builder()
                        .calledNumber(mobile)
                        .calledShowNumber(showNumber)
                        .ttsCode(voiceTemplate.getTemplateCode())
                        .ttsParam(variable)
                        .playTimes(3)
                        .volume(100)
                        .speed(5)
                        .outId("lianlian")
                        .build();
                CompletableFuture<SingleCallByTtsResponse> response = asyncClient.singleCallByTts(singleCallByTtsRequest);
                SingleCallByTtsResponse res = response.get();
                record.setBizId(res.getBody().getCallId());
                if ("OK".equals(res.getBody().getCode())) {
                    record.setStatus(NotifyRecord.STATUS_CONFIRM);
                } else {
                    record.setStatus(NotifyRecord.STATUS_FAILED);
                    record.setErrorCode(StringUtils.sub(res.getBody().getCode(), NotifyRecord.ERROR_CODE_LEN));
                    amount += Constant.TTS_UNIT_PRICE;
                }
            } catch (Exception e) {
                record.setStatus(NotifyRecord.STATUS_FAILED);
                record.setErrorMsg(StringUtils.sub(e.getMessage(), NotifyRecord.ERROR_MSG_LEN));
                amount += Constant.TTS_UNIT_PRICE;
            } finally {
                Calendar calendar = Calendar.getInstance();
                record.setNotifyTime(calendar.getTime());
                notifyRecordDao.insertSelective(record);

                // 重复推送
                for (int i = 0; i < times; i ++) {
                    calendar.add(Calendar.MINUTE, interval);
                    NotifyRecord preNotify = new NotifyRecord();
                    preNotify.setEventId(record.getEventId());
                    preNotify.setDeviceId(record.getDeviceId());
                    preNotify.setToUser(record.getToUser());
                    preNotify.setMethod(record.getMethod());
                    preNotify.setTemplateCode(record.getTemplateCode());
                    preNotify.setStatus(NotifyRecord.STATUS_WAIT);
                    preNotify.setParams(record.getParams());
                    preNotify.setContent(record.getContent());
                    preNotify.setNotifyTime(calendar.getTime());
                    notifyRecordDao.insertSelective(preNotify);
                }
            }
        }
        return amount;
    }

    private void apiPush(String deviceId, String eventId, Date eventTime, JSONObject input) {
        String items[] = deviceId.split("/");
        SecretKey secretKey = secretKeyDao.deviceMatch(items[0], items[1]);
        if (ObjectUtil.isNull(secretKey) || ObjectUtil.isEmpty(secretKey.getPushUrl()))
            return;
        input.remove("ts");
        input.remove("state");
        JSONObject payload = JSONUtil.createObj().set("eventId", eventId);
        if (!input.isEmpty()) {
            payload.set("params", input);
        }
        ApiPuh apiPuh = new ApiPuh();
        String params = JSONUtil.createObj()
                .set("msgType", "event")
                .set("requestId", apiPuh.getId())
                .set("deviceId", deviceId)
                .set("timestamp", eventTime.getTime())
                .set("payload", payload).toString();
        String md5 = MD5.create().digestHex(params.concat(secretKey.getSecret()));
        apiPuh.setDeviceId(deviceId);
        apiPuh.setUrl(String.format("%s?signature=%s", secretKey.getPushUrl(), md5));
        apiPuh.setParams(params);
        apiPushService.push(apiPuh);
    }

    private boolean isToday(Date date) {
        if (ObjectUtil.isNull(date))    return false;
        return ObjectUtil.equals(DateUtil.format(new Date(), DatePattern.NORM_DATE_PATTERN), DateUtil.format(date, DatePattern.NORM_DATE_PATTERN));
    }

    private String shortName(String deviceName) {
        return deviceName.substring(Math.max(deviceName.length() - 4, 0));
    }
}
